﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FlyweightPattern;

/// <summary>
/// 准备个容器，数据复用
/// 单线程下可以，但是多线程就会有问题了
/// </summary>
/*public class FlyweightFactory
{
    private static Dictionary<WordType, BaseWord> FlyweightFactoryDictionary = new Dictionary<WordType, BaseWord>();
    public static BaseWord CreateWord(WordType wordType)
    {
        BaseWord word = null;
        if (!FlyweightFactoryDictionary.ContainsKey(wordType))
        {
            switch (wordType)
            {
                case WordType.E:
                    word = new E();
                    break;
                case WordType.L:
                    word = new L();
                    break;
                case WordType.V:
                    word = new V();
                    break;
                case WordType.N:
                    word = new N();
                    break;
                default:
                    throw new ArgumentException("wrong wordType");
            }
            FlyweightFactoryDictionary[wordType] = word;
            return word;
        }
        else
        {
            return FlyweightFactoryDictionary[wordType];
        }
    }
}
*/

/*public class FlyweightFactory
{
    // 多线程版本
    private static Dictionary<WordType, BaseWord> FlyweightFactoryDictionary = new Dictionary<WordType, BaseWord>();
    private static readonly object locker = new object();
    public static BaseWord CreateWord(WordType wordType)
    {
        if (!FlyweightFactoryDictionary.ContainsKey(wordType))
        {
            lock (locker)
            {
                BaseWord word = null;
                if (!FlyweightFactoryDictionary.ContainsKey(wordType))
                {
                    switch (wordType)
                    {
                        case WordType.E:
                            word = new E();
                            break;
                        case WordType.L:
                            word = new L();
                            break;
                        case WordType.V:
                            word = new V();
                            break;
                        case WordType.N:
                            word = new N();
                            break;
                        default:
                            throw new ArgumentException("wrong wordType");
                    }
                    FlyweightFactoryDictionary[wordType] = word;
                    //return word;
                }
            }
        }
        return FlyweightFactoryDictionary[wordType];
    }
}*/

/// <summary>
/// 多线程安全的享元工厂，使用ConcurrentDictionary
/// 你遇到的问题是：即使使用了 ConcurrentDictionary 和 GetOrAdd，在高并发下，new E()、new L() 等对象还是可能被多次创建。
/// 这是因为 GetOrAdd 的 valueFactory（即 type => new E() 这部分）在并发时可能会被多次调用，但只有一个对象会被存入字典，其余的会被丢弃。
/// </summary>
/*public class FlyweightFactory
{
    private static readonly ConcurrentDictionary<WordType, BaseWord> FlyweightFactoryDictionary = new();

    /// <summary>
    /// 这种方式是线程安全的，但是 •	ConcurrentDictionary.GetOrAdd 的 valueFactory 不是原子操作，可能会被多线程同时调用，导致多次 new。
    /// </summary>
    /// <param name="wordType"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentException"></exception>
    public static BaseWord CreateWord(WordType wordType)
    {
        return FlyweightFactoryDictionary.GetOrAdd(wordType, static type => type switch
        {
            WordType.E => new E(),
            WordType.L => new L(),
            WordType.V => new V(),
            WordType.N => new N(),
            _ => throw new ArgumentException("wrong wordType")
        });
    }
}*/
/**
 * 如果你只关心字典里是否只有一个实例，这种实现是没问题的。如果你希望全局只创建一次对象（即 new 只执行一次），可以提前初始化，或者用 Lazy<T>。
推荐做法：用 Lazy<T> 保证只创建一次
 */

public class FlyweightFactory
{
    private static readonly ConcurrentDictionary<WordType, Lazy<BaseWord>> FlyweightFactoryDictionary = new();

    public static BaseWord CreateWord(WordType wordType)
    {
        var lazy = FlyweightFactoryDictionary.GetOrAdd(wordType, static type => new Lazy<BaseWord>(() => type switch
        {
            WordType.E => new E(),
            WordType.L => new L(),
            WordType.V => new V(),
            WordType.N => new N(),
            _ => throw new ArgumentException("wrong wordType")
        }, true));
        return lazy.Value;
    }
}


public enum WordType
{
    E,
    L,
    V,
    N
}
